package org.glassunit.core.deploy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Enterprise Application module.
 * @author Frederic Müller
 */
public class ApplicationModule extends Module {

    private static final String DESCRIPTOR_PATH = "META-INF";
    private static final String DESCRIPTOR_FILE = DESCRIPTOR_PATH + "/application.xml";
    private static final String PROPERTY_KEY_TYPE = ApplicationModule.class.getName() + "_type";
    private static final String PROPERTY_VALUE_TYPE_EJB = "ejb";
    private static final String PROPERTY_VALUE_TYPE_WAR = "war";
    private static final String PROPERTY_KEY_CONTEXT_ROOT = ApplicationModule.class.getName() + "_context_root";
    private byte[] descriptor = null;

    /**
     * Creates a new application module with the given name.
     * @param name application name
     */
    public ApplicationModule(final String name) {
        super(name);
    }

    @Override
    public InputStream getFile(final String filename) throws IOException {
        if (DESCRIPTOR_FILE.equals(filename)) {
            return new ByteArrayInputStream(descriptor);
        } else {
            return super.getFile(filename);
        }
    }

    @Override
    public Set<String> getFiles() {
        final Set<String> items = new HashSet<String>();
        items.add(DESCRIPTOR_FILE);
        items.addAll(super.getFiles());
        return items;
    }

    @Override
    public Set<String> getDirectories() {
        final Set<String> items = new HashSet<String>();
        items.add(DESCRIPTOR_PATH);
        items.addAll(super.getDirectories());
        return items;
    }

    @Override
    public Long getFileSize(final String filename) {
        if (DESCRIPTOR_FILE.equals(filename)) {
            return Long.valueOf(descriptor.length);
        } else {
            return super.getFileSize(filename);
        }
    }

    /**
     * Adds a new ejb module to the application.
     * @param module ejb module
     */
    public void addEjbModule(final Module module) {
        module.getProperties().put(PROPERTY_KEY_TYPE, PROPERTY_VALUE_TYPE_EJB);
        getSubModules().add(module);
        updateDescriptor();
    }

    /**
     * Adds a new web module to the application.
     * @param module web module
     */
    public void addWebModule(final Module module) {
        addWebModule(module, null);
    }

    /**
     * Adds a new web module to the application with the given context root.
     * @param module web module
     * @param contextRoot context root
     */
    public void addWebModule(final Module module, final String contextRoot) {
        module.getProperties().put(PROPERTY_KEY_TYPE, PROPERTY_VALUE_TYPE_WAR);
        if (contextRoot != null) {
            module.getProperties().put(PROPERTY_KEY_CONTEXT_ROOT, contextRoot);
        }
        getSubModules().add(module);
        updateDescriptor();
    }

    /**
     * Updates the application descriptor (application.xml).
     */
    private void updateDescriptor() {
        try {
            final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

            final Element displayNameElement = document.createElement("display-name");
            displayNameElement.setTextContent(getName());

            final Element applicationElement = document.createElement("application");
            applicationElement.appendChild(displayNameElement);

            for (Module module : getSubModules()) {
                if (PROPERTY_VALUE_TYPE_EJB.equals(module.getProperties().get(PROPERTY_KEY_TYPE))) {
                    final Element moduleElement = document.createElement("module");
                    final Element ejbElement = document.createElement("ejb");
                    ejbElement.setTextContent(module.getName());
                    moduleElement.appendChild(ejbElement);
                    applicationElement.appendChild(moduleElement);
                }
                if (PROPERTY_VALUE_TYPE_WAR.equals(module.getProperties().get(PROPERTY_KEY_TYPE))) {
                    final Element moduleElement = document.createElement("module");
                    final Element warElement = document.createElement("web");
                    final Element uriElement = document.createElement("web-uri");

                    uriElement.setTextContent(module.getName());
                    warElement.appendChild(uriElement);
                    final Element contextRootElement = document.createElement("context-root");
                    if (module.getProperties().contains(PROPERTY_KEY_CONTEXT_ROOT)) {
                        contextRootElement.setTextContent(module.getProperties().getProperty(PROPERTY_KEY_CONTEXT_ROOT));
                    } else {
                        contextRootElement.setTextContent(module.getName());
                    }
                    warElement.appendChild(contextRootElement);

                    moduleElement.appendChild(warElement);
                    applicationElement.appendChild(moduleElement);
                }
            }

            document.appendChild(applicationElement);

            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://java.sun.com/dtd/application_1_3.dtd");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.transform(new DOMSource(document), new StreamResult(outputStream));
            outputStream.close();

            descriptor = outputStream.toByteArray();
        } catch (final ParserConfigurationException ex) {
            throw new RuntimeException("Could not create descriptor document", ex);
        } catch (final TransformerException ex) {
            throw new RuntimeException("Could not transform desriptor document", ex);
        } catch (final IOException ex) {
            throw new RuntimeException("Could not write descriptor", ex);
        }

    }

    /**
     * Return the application descriptor (application.xml).
     * @return descriptor
     */
    public String getDescriptor() {
        return new String(descriptor);
    }
}
